package com.huajx.core.codec;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.huajx.util.StrUtil;
import com.huajx.util.convert.Convert;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;

import com.huajx.core.model.HttpRequestMessage;

public class HttpRequestDecoder extends MessageDecoderAdapter {
    private static final byte[] CONTENT_LENGTH = new String("Content-Length:").getBytes();
    public static String defaultEncoding;
    private CharsetDecoder decoder;

    public CharsetDecoder getDecoder() {
        return decoder;
    }

    public void setEncoder(CharsetDecoder decoder) {
        this.decoder = decoder;
    }

    private HttpRequestMessage request = null;

    public HttpRequestDecoder() {
        decoder = Charset.forName(defaultEncoding).newDecoder();
    }

    public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
        try {
            return messageComplete(in) ? MessageDecoderResult.OK : MessageDecoderResult.NEED_DATA;
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return MessageDecoderResult.NOT_OK;
    }

    public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
        HttpRequestMessage m = decodeBody(in);

        // Return NEED_DATA if the body is not fully read.
        if (m == null) {
            return MessageDecoderResult.NEED_DATA;
        }

        out.write(m);

        return MessageDecoderResult.OK;

    }

    /*
     * 判断HTTP请求是否完整，若格式有错误直接抛出异常
     */
    private boolean messageComplete(IoBuffer in) {
        int last = in.remaining() - 1;
        if (in.remaining() < 4) {
            return false;
        }

        // to speed up things we check if the Http request is a GET or POST
        if (in.get(0) == (byte) 'G' && in.get(1) == (byte) 'E' && in.get(2) == (byte) 'T') {
            // Http GET request therefore the last 4 bytes should be 0x0D 0x0A
            // 0x0D 0x0A
            return in.get(last) == (byte) 0x0A && in.get(last - 1) == (byte) 0x0D && in.get(last - 2) == (byte) 0x0A
                    && in.get(last - 3) == (byte) 0x0D;
        } else if (in.get(0) == (byte) 'P' && in.get(1) == (byte) 'O' && in.get(2) == (byte) 'S'
                && in.get(3) == (byte) 'T') {
            // Http POST request
            // first the position of the 0x0D 0x0A 0x0D 0x0A bytes
            int eoh = -1;
            for (int i = last; i > 2; i--) {
                if (in.get(i) == (byte) 0x0A && in.get(i - 1) == (byte) 0x0D && in.get(i - 2) == (byte) 0x0A
                        && in.get(i - 3) == (byte) 0x0D) {
                    eoh = i + 1;
                    break;
                }
            }
            if (eoh == -1) {
                return false;
            }
            for (int i = 0; i < last; i++) {
                boolean found = false;
                for (int j = 0; j < CONTENT_LENGTH.length; j++) {
                    if (in.get(i + j) != CONTENT_LENGTH[j]) {
                        found = false;
                        break;
                    }
                    found = true;
                }
                if (found) {
                    // retrieve value from this position till next 0x0D 0x0A
                    StringBuilder contentLength = new StringBuilder();
                    for (int j = i + CONTENT_LENGTH.length; j < last; j++) {
                        if (in.get(j) == 0x0D) {
                            break;
                        }
                        contentLength.append(new String(new byte[]{in.get(j)}));
                    }
                    // if content-length worth of data has been received then
                    // the message is complete
                    return Integer.parseInt(contentLength.toString().trim()) + eoh == in.remaining();
                }
            }
        } else if (in.get(0) == (byte) 'O' && in.get(1) == (byte) 'P' && in.get(2) == (byte) 'T'
                && in.get(3) == (byte) 'I' && in.get(4) == (byte) 'O'
                && in.get(5) == (byte) 'N' && in.get(6) == (byte) 'S') {
            return true;
        }

        // the message is not complete and we need more data
        return false;

    }

    private HttpRequestMessage decodeBody(IoBuffer in) {
        request = new HttpRequestMessage();
        try {
            request.setHeaders(parseRequest(new StringReader(in.getString(decoder))));
            return request;
        } catch (CharacterCodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    private Map<String, String[]> parseRequest(StringReader is) {
        Map<String, String[]> map = new HashMap<String, String[]>();
        BufferedReader rdr = new BufferedReader(is);

//        boolean isGet = false;

        try {
            // Get request URL.
            String line = rdr.readLine();
            String[] url = line.split(" ");
            if (url.length < 3) {
                return map;
            }

            String context = formatContext(url[1].substring(1));

            int idx = context.indexOf("?");
            if (idx > -1) {
//                map.put("Context", new String[]{context.substring(0, idx)});
                if(idx + 1 < context.length()){
                    parseParams(context.substring(idx + 1), map);
                    context = context.substring(0, idx);
                }
            }

            map.put("URI", new String[]{line});
            map.put("Method", new String[]{url[0].toUpperCase()});
            map.put("Context", new String[]{context});
            map.put("Protocol", new String[]{url[2]});
            // Read header
            while ((line = rdr.readLine()) != null && line.length() > 0) {
                String[] tokens = line.split(": ");
                map.put(tokens[0], new String[]{tokens[1]});
            }

            // If method 'POST' then read Content-Length worth of data
            if (url[0].equalsIgnoreCase("POST")) {
                int len = Integer.parseInt(map.get("Content-Length")[0]);
                if (len > 0) {
                    char[] buf = new char[len];
                    rdr.read(buf);
                    line = String.copyValueOf(buf);
                }

//				if (rdr.read(buf) == len) {
//					line = String.copyValueOf(buf);
//				}
            } else if (url[0].equalsIgnoreCase("GET")) {
//                isGet = true;
//
//                int idx = url[1].indexOf('?');
//                if (idx != -1) {
//                    map.put("Context", new String[]{url[1].substring(1, idx)});
//                    line = url[1].substring(idx + 1);
//                } else {
//                    line = null;
//                }
            } else if (url[0].equalsIgnoreCase("OPTIONS")) {
                return map;
            }

//            if (isGet && StrUtil.isNotBlank(line)) {
//                parseParams(line, map);
//            } else {
//                int idx = context.indexOf("?");
//                if (idx > -1) {
//                    map.put("Context", new String[]{context.substring(0, idx)});
//                    if(idx + 1 < context.length()){
//                        parseParams(context.substring(idx + 1), map);
//                    }
//                }
//            }

            if (StrUtil.isBlank(line)) {
                return map;
            }

            parseJson(line, map);

//            if (!isGet) {
//
//            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        return map;
    }

    private String formatContext(String context) {
//        context = context.startsWith("/") ? context : "/" + context;
        context = context.replace("//", "/");
        if (context.endsWith("/")) {
            context = context.substring(0, context.indexOf("/"));
        }
        return context;
    }

    private void parseJson(String line, Map<String, String[]> map) {
        try {
            JSONObject jso = JSON.parseObject(line);

            Set<Map.Entry<String, Object>> entries = jso.entrySet();
            for (Map.Entry<String, Object> entry : entries){
                Object value = entry.getValue();

                map.put("@"+entry.getKey(), new String[]{Convert.toStr(value, "")});
            }

//            for (int i = 0; i < jso.size(); i++) {
//                map.put("@", jso.keySet())
//            }
        } catch (Throwable e) {
        }
        String[] params = new String[1];
    }

    private void parseParams(String line, Map<String, String[]> map) throws UnsupportedEncodingException {
        String[] match = line.split("\\&");
        for (String element : match) {
            String[] params = new String[1];
            String[] tokens = element.split("=");
            switch (tokens.length) {
                case 0:
                    map.put("@".concat(element), new String[]{});
                    break;
                case 1:
                    map.put("@".concat(tokens[0]), new String[]{});
                    break;
                default:
                    String name = "@".concat(tokens[0]);
                    if (map.containsKey(name)) {
                        params = map.get(name);
                        String[] tmp = new String[params.length + 1];
                        for (int j = 0; j < params.length; j++) {
                            tmp[j] = URLDecoder.decode(params[j], "UTF-8");//params[j];
//								System.out.println(tmp[j]+"----"+ URLDecoder.decode(tmp[j], "UTF-8") + "----"+URLEncoder.encode(tmp[j], "UTF-8"));
                        }
                        params = null;
                        params = tmp;
                    }
                    params[params.length - 1] = URLDecoder.decode(tokens[1].trim(), "UTF-8");
                    map.put(name, params);
//						System.out.println( tokens[1]+"----"+ URLDecoder.decode( tokens[1], "UTF-8") + "----"+URLEncoder.encode( tokens[1], "UTF-8"));
            }
        }
    }
}
